/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "gamelib.h"


/*
 ==================
 Str_Length
 ==================
*/
int Str_Length (const char *string){

	const char	*s;

	if (!string)
		return 0;

	s = string;
	while (*s)
		s++;

	return s - string;
}

/*
 ==================
 Str_LengthWithoutColors

 Like Str_Length, but discounts color sequences
 ==================
*/
int Str_LengthWithoutColors (const char *string){

	int		len = 0;

	if (!string)
		return 0;

	while (*string){
		if (Str_IsColor(string)){
			string += 2;
			continue;
		}

		string++;
		len++;
	}

	return len;
}

/*
 ==================
 Str_RemoveColors

 Removes color sequences from string
 ==================
*/
void Str_RemoveColors (char *string){

	char	*src, *dst;

	if (!string)
		return;

	src = string;
	dst = string;

	while (*src){
		if (Str_IsColor(src)){
			src += 2;
			continue;
		}

		*dst++ = *src++;
	}

	*dst = 0;
}

/*
 ==================
 Str_Copy

 Ensures a trailing zero
 ==================
*/
void Str_Copy (char *dst, const char *src, int dstSize){

	if (!dst || dstSize < 1)
		return;

	if (!src){
		*dst = 0;
		return;
	}

	while (--dstSize && *src)
		*dst++ = *src++;

	*dst = 0;
}

/*
 ==================
 Str_Append

 Ensures a trailing zero
 ==================
*/
void Str_Append (char *dst, const char *src, int dstSize){

	if (!dst || dstSize < 1)
		return;

	if (!src)
		return;

	while (--dstSize && *dst)
		dst++;

	if (dstSize > 0){
		while (--dstSize && *src)
			*dst++ = *src++;

		*dst = 0;
	}
}

/*
 ==================
 Str_Compare
 ==================
*/
int Str_Compare (const char *string1, const char *string2){

	int		c1, c2;

	if (!string1){
		if (!string2)
			return 0;
		else
			return -1;
	}
	else if (!string2)
		return 1;

	do {
		c1 = *string1++;
		c2 = *string2++;

		if (c1 != c2)
			return c1 < c2 ? -1 : 1;	// Strings not equal
	} while (c1);

	return 0;	// Strings are equal
}

/*
 ==================
 Str_CompareChars
 ==================
*/
int Str_CompareChars (const char *string1, const char *string2, int n){

	int		c1, c2;

	if (!string1){
		if (!string2)
			return 0;
		else
			return -1;
	}
	else if (!string2)
		return 1;

	do {
		c1 = *string1++;
		c2 = *string2++;

		if (!n--)
			return 0;	// Strings are equal until end point

		if (c1 != c2)
			return c1 < c2 ? -1 : 1;	// Strings not equal
	} while (c1);

	return 0;	// Strings are equal
}

/*
 ==================
 Str_ICompare
 ==================
*/
int Str_ICompare (const char *string1, const char *string2){

	int		c1, c2;

	if (!string1){
		if (!string2)
			return 0;
		else
			return -1;
	}
	else if (!string2)
		return 1;

	do {
		c1 = *string1++;
		c2 = *string2++;

		if (c1 != c2){
			if (c1 >= 'a' && c1 <= 'z')
				c1 -= ('a' - 'A');
			if (c2 >= 'a' && c2 <= 'z')
				c2 -= ('a' - 'A');

			if (c1 != c2)
				return c1 < c2 ? -1 : 1;	// Strings not equal
		}
	} while (c1);

	return 0;	// Strings are equal
}

/*
 ==================
 Str_ICompareChars
 ==================
*/
int Str_ICompareChars (const char *string1, const char *string2, int n){

	int		c1, c2;

	if (!string1){
		if (!string2)
			return 0;
		else
			return -1;
	}
	else if (!string2)
		return 1;

	do {
		c1 = *string1++;
		c2 = *string2++;

		if (!n--)
			return 0;	// Strings are equal until end point

		if (c1 != c2){
			if (c1 >= 'a' && c1 <= 'z')
				c1 -= ('a' - 'A');
			if (c2 >= 'a' && c2 <= 'z')
				c2 -= ('a' - 'A');

			if (c1 != c2)
				return c1 < c2 ? -1 : 1;	// Strings not equal
		}
	} while (c1);

	return 0;	// Strings are equal
}

/*
 ==================
 Str_ToLower
 ==================
*/
void Str_ToLower (char *string){

	if (!string)
		return;

	while (*string){
		if (*string >= 'A' && *string <= 'Z')
			*string += 'a' - 'A';

		string++;
	}
}

/*
 ==================
 Str_ToUpper
 ==================
*/
void Str_ToUpper (char *string){

	if (!string)
		return;

	while (*string){
		if (*string >= 'a' && *string <= 'z')
			*string += 'A' - 'a';

		string++;
	}
}

/*
 ==================
 Str_ToFloat
 ==================
*/
float Str_ToFloat (const char *string){

	double	sign = 1.0, value = 0.0;
	double	fraction = 0.1, power = 1.0;
	bool	negative = false;
	int		i, exponent = 0;

	if (!string)
		return 0.0f;

	// Skip whitespace
	while (*string <= ' '){
		if (!*string)
			return 0.0f;

		string++;
	}

	// Check sign
	if (*string == '+'){
		string++;

		sign = 1.0;
	}
	else if (*string == '-'){
		string++;

		sign = -1.0;
	}

	// Read decimal part
	if (*string != '.'){
		while (*string){
			if (*string < '0' || *string > '9')
				break;

			value = value * 10.0 + (double)(*string++ - '0');
		}
	}

	// Read fractional part
	if (*string == '.'){
		string++;

		while (*string){
			if (*string < '0' || *string > '9')
				break;

			value = value + (double)(*string++ - '0') * fraction;
			fraction *= 0.1;
		}
	}

	// Read exponent
	if (*string == 'e' || *string == 'E'){
		string++;

		if (*string == '+'){
			string++;

			negative = false;
		}
		else if (*string == '-'){
			string++;

			negative = true;
		}

		while (*string){
			if (*string < '0' || *string > '9')
				break;

			exponent = exponent * 10 + (*string++ - '0');
		}

		for (i = 0; i < exponent; i++)
			power *= 10.0;

		if (negative)
			value /= power;
		else
			value *= power;
	}

	return (float)(sign * value);
}

/*
 ==================
 Str_ToInteger
 ==================
*/
int Str_ToInteger (const char *string){

	int		sign = 1, value = 0;

	if (!string)
		return 0;

	// Skip whitespace
	while (*string <= ' '){
		if (!*string)
			return 0;

		string++;
	}

	// Check sign
	if (*string == '+'){
		string++;

		sign = 1;
	}
	else if (*string == '-'){
		string++;

		sign = -1;
	}

	// Read digits
	while (*string){
		if (*string < '0' || *string > '9')
			break;

		value = value * 10 + (*string++ - '0');
	}

	return sign * value;
}

/*
 ==================
 Str_FromFloat
 ==================
*/
const char *Str_FromFloat (float value){

	static char	string[8][64];		// In case this is called by nested functions
	static int	index;
	char		*dst, tmp[64];
	int			val, digits, len = 0;

	dst = string[index];
	index = (index + 1) & 7;

	// Check sign
	if (value < 0.0f){
		dst[len++] = '-';

		value = -value;
	}

	val = M_Ftol(value);

	// Write decimal part
	digits = 0;
	while (1){
		tmp[digits++] = '0' + val % 10;
		val /= 10;

		if (!val)
			break;
	}

	while (digits--)
		dst[len++] = tmp[digits];

	dst[len++] = '.';

	// Write fractional part
	digits = 0;
	while (digits < 6){
		digits++;

		value = (value - M_Ftol(value)) * 10.0f;

		dst[len++] = '0' + M_Ftol(value) % 10;
	}

	// Remove trailing zeros
	while (len > 0){
		if (dst[len-1] != '0'){
			// Make sure one digit remains
			if (dst[len-1] == '.')
				dst[len++] = '0';

			break;
		}

		dst[--len] = 0;
	}

	dst[len] = 0;

	return dst;
}

/*
 ==================
 Str_FromInteger
 ==================
*/
const char *Str_FromInteger (int value){

	static char	string[8][64];		// In case this is called by nested functions
	static int	index;
	char		*dst, tmp[64];
	int			digits, len = 0;

	dst = string[index];
	index = (index + 1) & 7;

	// Check sign
	if (value < 0){
		dst[len++] = '-';

		value = -value;
	}

	// Write digits
	digits = 0;
	while (1){
		tmp[digits++] = '0' + value % 10;
		value /= 10;

		if (!value)
			break;
	}

	while (digits--)
		dst[len++] = tmp[digits];

	dst[len] = 0;

	return dst;
}

/*
 ==================
 Str_FromFloatArray
 ==================
*/
const char *Str_FromFloatArray (const float *values, int elements, int precision){

	static char	string[8][1024];	// In case this is called by nested functions
	static int	index;
	char		fmt[16];
	char		*dst;
	int			dstSize;
	int			i;

	if (precision > 0)
		Str_SPrintf(fmt, sizeof(fmt), "%%.%if", precision);

	dst = string[index];
	dstSize = sizeof(string[index]);
	index = (index + 1) & 7;

	dst[0] = 0;

	for (i = 0; i < elements; i++){
		if (precision > 0)
			Str_Append(dst, Str_VarArgs(fmt, values[i]), dstSize);
		else {
			if (precision == -1)
				Str_Append(dst, Str_VarArgs("%g", values[i]), dstSize);
			else
				Str_Append(dst, Str_VarArgs("%i", M_Ftol(values[i])), dstSize);
		}

		if (i != elements - 1)
			Str_Append(dst, " ", dstSize);
	}

	return dst;
}

/*
 ==================
 Str_FromIntegerArray
 ==================
*/
const char *Str_FromIntegerArray (const int *values, int elements){

	static char	string[8][1024];	// In case this is called by nested functions
	static int	index;
	char		*dst;
	int			dstSize;
	int			i;

	dst = string[index];
	dstSize = sizeof(string[index]);
	index = (index + 1) & 7;

	dst[0] = 0;

	for (i = 0; i < elements; i++){
		Str_Append(dst, Str_VarArgs("%i", values[i]), dstSize);

		if (i != elements - 1)
			Str_Append(dst, " ", dstSize);
	}

	return dst;
}

/*
 ==================
 Str_FindChar
 ==================
*/
char *Str_FindChar (const char *string, int c){

	if (!string || !c)
		return NULL;

	while (*string){
		if (*string == c)
			return (char *)string;

		string++;
	}

	return NULL;
}

/*
 ==================
 Str_FindText
 ==================
*/
char *Str_FindText (const char *string, const char *text, bool caseSensitive){

	int		c1, c2;
	int		i;

	if (!string || !text)
		return NULL;

	while (*string){
		if (caseSensitive){
			for (i = 0; text[i]; i++){
				if (string[i] != text[i])
					break;
			}
		}
		else {
			for (i = 0; text[i]; i++){
				if (string[i] != text[i]){
					if (string[i] >= 'a' && string[i] <= 'z')
						c1 = string[i] - ('a' - 'A');
					else
						c1 = string[i];

					if (text[i] >= 'a' && text[i] <= 'z')
						c2 = text[i] - ('a' - 'A');
					else
						c2 = text[i];

					if (c1 != c2)
						break;
				}
			}
		}

		if (!text[i])
			return (char *)string;

		string++;
	}

	return NULL;
}

/*
 ==================
 Str_SPrintf

 Ensures a trailing zero
 ==================
*/
int Str_SPrintf (char *dst, int dstSize, const char *fmt, ...){

	va_list	argPtr;
	int		len;

	if (!dst || dstSize < 1)
		return 0;

	va_start(argPtr, fmt);
	len = vsnprintf(dst, dstSize, fmt, argPtr);
	va_end(argPtr);

	dst[dstSize-1] = 0;

	if (len < 0 || len >= dstSize)
		return -1;

	return len;
}

/*
 ==================
 Str_VSPrintf

 Ensures a trailing zero
 ==================
*/
int Str_VSPrintf (char *dst, int dstSize, const char *fmt, va_list argPtr){

	int		len;

	if (!dst || dstSize < 1)
		return 0;

	len = vsnprintf(dst, dstSize, fmt, argPtr);

	dst[dstSize-1] = 0;

	if (len < 0 || len >= dstSize)
		return -1;

	return len;
}

/*
 ==================
 Str_VarArgs

 Does a varargs printf into a temporary buffer, so we don't need to have
 varargs versions of all text functions
 ==================
*/
const char *Str_VarArgs (const char *fmt, ...){

	static char	string[8][4096];	// In case this is called by nested functions
	static int	index;
	va_list		argPtr;
	char		*dst;
	int			dstSize;
	int			len;

	dst = string[index];
	dstSize = sizeof(string[index]);
	index = (index + 1) & 7;

	va_start(argPtr, fmt);
	len = vsnprintf(dst, dstSize, fmt, argPtr);
	va_end(argPtr);

	dst[dstSize-1] = 0;

	if (len < 0 || len >= dstSize)
		return "";

	return dst;
}

/*
 ==================
 Str_MatchFilterAfterStar

 Like Str_MatchFilter, but matches the given filter against any final segment
 of the given string
 ==================
*/
static bool Str_MatchFilterAfterStar (const char *string, const char *filter, bool caseSensitive){

	const char	*s;
	const char	*f;
	char		c1, c2;

	if (!string || !filter)
		return false;

	s = string;
	f = filter;

	while ((c1 = *f++) == '?' || c1 == '*'){
		if (c1 == '?' && *s++ == '\0')
			return false;
	}

	if (c1 == '\0')
		return true;

	if (c1 == '\\')
		c2 = *f;
	else
		c2 = c1;

	while (1){
		if (caseSensitive){
			if (c1 == '[' || *s == c2){
				if (Str_MatchFilter(s, f - 1, caseSensitive))
					return true;
			}
		}
		else {
			if (c1 == '[' || Str_CharToLower(*s) == Str_CharToLower(c2)){
				if (Str_MatchFilter(s, f - 1, caseSensitive))
					return true;
			}
		}

		if (*s++ == '\0')
			return false;
	}
}

/*
 ==================
 Str_MatchFilter

 Matches the given filter against the given string.
 Returns true if matches, false otherwise.

 A match means the entire string is used up in matching.

 In the filter string, '*' matches any sequence of characters, '?' matches any
 character, '[SET]' matches any character in the specified set, '[!SET]'
 matches any character not in the specified set.

 A set is composed of characters or ranges. A range contains a hyphen character
 (as in 0-9 or A-Z).
 [0-9a-zA-Z_] is the set of characters allowed in C/C++ identifiers.
 Any other character in the filter must be matched exactly.

 To suppress the special syntactic significance of any of *?[]!-\, and match
 the character exactly, precede it with a '\'.
 ==================
*/
bool Str_MatchFilter (const char *string, const char *filter, bool caseSensitive){

	const char	*s;
	const char	*f;
	char		c1, c2, start, end;
	bool		invert;

	if (!string || !filter)
		return false;

	s = string;
	f = filter;

	while ((c1 = *f++) != '\0'){
		switch (c1){
		case '*':
			return Str_MatchFilterAfterStar(s, f, caseSensitive);
		case '?':
			if (*s == '\0')
				return false;
			else
				++s;

			break;
		case '[':
			c2 = *s++;
			if (!c2)
				return false;

			invert = (*f == '!');
			if (invert)
				f++;

			c1 = *f++;
			while (1){
				start = c1;
				end = c1;

				if (c1 == '\\'){
					start = *f++;
					end = start;
				}
				if (c1 == '\0')
					return false;

				c1 = *f++;
				if (c1 == '-' && *f != ']'){
					end = *f++;
					if (end == '\\')
						end = *f++;
					if (end == '\0')
						return false;

					c1 = *f++;
				}

				if (caseSensitive){
					if (c2 >= start && c2 <= end)
						goto match;
				}
				else {
					if (Str_CharToLower(c2) >= Str_CharToLower(start) && Str_CharToLower(c2) <= Str_CharToLower(end))
						goto match;
				}

				if (c1 == ']')
					break;
			}

			if (!invert)
				return false;

			break;

match:
			while (c1 != ']'){
				if (c1 == '\0')
					return false;

				c1 = *f++;
				if (c1 == '\0')
					return false;
				else if (c1 == '\\')
					++f;
			}

			if (invert)
				return false;

			break;
		case '\\':
			if (caseSensitive){
				if (*f++ != *s++)
					return false;
			}
			else {
				if (Str_CharToLower(*f++) != Str_CharToLower(*s++))
					return false;
			}

			break;
		default:
			if (caseSensitive){
				if (c1 != *s++)
					return false;
			}
			else {
				if (Str_CharToLower(c1) != Str_CharToLower(*s++))
					return false;
			}

			break;
		}
	}

	if (*s != '\0')
		return false;

	return true;
}

/*
 ==================
 Str_HashKey

 Returns a hash key for the given string
 ==================
*/
uint Str_HashKey (const char *string, uint hashSize, bool caseSensitive){

	uint	hashKey = 0;
	int		i, c;

	if (!string)
		return 0;

	if (caseSensitive){
		for (i = 0; string[i]; i++){
			c = string[i];

			hashKey += (i + 119) * c;
		}
	}
	else {
		for (i = 0; string[i]; i++){
			c = Str_CharToLower(string[i]);

			hashKey += (i * 119) * c;
		}
	}

	hashKey = (hashKey ^ (hashKey >> 10)) ^ (hashKey >> 20);
	hashKey &= (hashSize - 1);

	return hashKey;
}

/*
 ==================
 Str_StripFilePath

 Removes the file path, if any
 ==================
*/
void Str_StripFilePath (char *path){

	char	*p;

	if (!path)
		return;

	p = path + Str_Length(path);

	while (p != path){
		if (*p == '/' || *p == '\\')
			break;

		p--;
	}

	if (*p == '/' || *p == '\\')
		p++;

	while (*p)
		*path++ = *p++;

	*path = 0;
}

/*
 ==================
 Str_StripFileExtension

 Removes the file extension, if any
 ==================
*/
void Str_StripFileExtension (char *path){

	char	*p;

	if (!path)
		return;

	p = path + Str_Length(path);

	while (p != path){
		if (*p == '/' || *p == '\\')
			break;

		if (*p == '.'){
			*p = 0;
			break;
		}

		p--;
	}
}

/*
 ==================
 Str_DefaultFilePath

 If the path doesn't have a / or a \\, inserts defaultPath (which should not
 include the '/')
 ==================
*/
void Str_DefaultFilePath (char *path, int pathSize, const char *defaultPath){

	char	copy[MAX_OSPATH];
	char	*p;

	if (!path || pathSize < 1 || !defaultPath)
		return;

	p = path;

	while (*p){
		if (*p == '/' || *p == '\\')
			return;		// It has a path
		
		p++;
	}

	Str_Copy(copy, path, sizeof(copy));
	Str_SPrintf(path, pathSize, "%s/%s", defaultPath, copy);
}

/*
 ==================
 Str_DefaultFileExtension

 If the path doesn't have a .EXT, appends defaultExtension (which should
 include the '.')
 ==================
*/
void Str_DefaultFileExtension (char *path, int pathSize, const char *defaultExtension){

	char	*p;

	if (!path || pathSize < 1 || !defaultExtension)
		return;

	p = path + Str_Length(path);

	while (p != path){
		if (*p == '/' || *p == '\\')
			break;

		if (*p == '.')
			return;		// It has an extension

		p--;
	}

	Str_Append(path, defaultExtension, pathSize);
}

/*
 ==================
 Str_ExtractFilePath

 Extracts the file path
 ==================
*/
void Str_ExtractFilePath (const char *src, char *dst, int dstSize){

	const char	*s;

	if (!dst || dstSize < 1)
		return;

	if (!src){
		*dst = 0;
		return;
	}

	s = src + Str_Length(src);

	while (s != src){
		if (*s == '/' || *s == '\\')
			break;

		s--;
	}

	if (s - src < dstSize)
		Str_Copy(dst, src, (s - src) + 1);
	else
		Str_Copy(dst, src, dstSize);
}

/*
 ==================
 Str_ExtractFileName

 Extracts the file name
 ==================
*/
void Str_ExtractFileName (const char *src, char *dst, int dstSize){

	const char	*s;

	if (!dst || dstSize < 1)
		return;

	if (!src){
		*dst = 0;
		return;
	}

	s = src + Str_Length(src);

	while (s != src){
		if (*s == '/' || *s == '\\')
			break;

		s--;
	}

	if (*s == '/' || *s == '\\')
		s++;

	Str_Copy(dst, s, dstSize);
}

/*
 ==================
 Str_ExtractFileBase

 Extracts the file name minus the extension
 ==================
*/
void Str_ExtractFileBase (const char *src, char *dst, int dstSize){

	const char	*s, *p;

	if (!dst || dstSize < 1)
		return;

	if (!src){
		*dst = 0;
		return;
	}

	s = src + Str_Length(src);
	p = NULL;

	while (s != src){
		if (*s == '/' || *s == '\\')
			break;

		if (!p && *s == '.')
			p = s;

		s--;
	}

	if (*s == '/' || *s == '\\')
		s++;

	if (p && p - s < dstSize)
		Str_Copy(dst, s, (p - s) + 1);
	else
		Str_Copy(dst, s, dstSize);
}

/*
 ==================
 Str_ExtractFileExtension

 Extracts the file extension
 ==================
*/
void Str_ExtractFileExtension (const char *src, char *dst, int dstSize){

	const char	*s, *p;

	if (!dst || dstSize < 1)
		return;

	if (!src){
		*dst = 0;
		return;
	}

	s = src + Str_Length(src);
	p = s;

	while (s != src){
		if (*s == '/' || *s == '\\')
			break;

		if (*s == '.'){
			p = s;
			break;
		}

		s--;
	}

	Str_Copy(dst, p, dstSize);
}
